<?php
/**
 * ESDBRequest class file
 *
 * @author Yoyon Cahyono <yoyoncahyono@gmail.com>
 * @link http://ondev.enggon.com/
 * @copyright Copyright &copy; 2011 Enggon.com
 * @license http://www.enggon.com/license/
 */

class ESDBRequest {
	/**
	 * @var Object SimpleDB response
	 */
	public $response;
	/**
	 * @var ESDBConnection SimpleDB request connection identifier
	 */
	private $_connection;
	/**
	 * @var string SimpleDB REST request method
	 */
	private $_method;
	/**
	 * @var array SimpleDB request parameters
	 */
	private $_parametes = array();
	/**
	 * @var array SimpleDB request latest errors
	 */
	private $_errors = false;
	
	/**
	 * Constructor
	 * @param ESDBConnection $sdbConn SimpleDB connection instance
	 * @param string $action SimpleDB action
	 * @param string $method HTTP request method
	 * @param string $domain Domain name
	 */
	function __construct($sdbConn, $action, $method, $domain = null) {
		if(!empty($domain))
		{
			$this->_parameters['DomainName'] = $domain;
		}
	
		$this->_parameters['Action'] = $action;
		$this->_parameters['Version'] = '2009-04-15';
		$this->_parameters['SignatureVersion'] = '2';
		$this->_parameters['SignatureMethod'] = 'HmacSHA256';
		$this->_parameters['AWSAccessKeyId'] = $sdbConn->accessKey;
	
		$this->_connection= $sdbConn;
		$this->_method = $method;

		$this->response = new stdClass();
	}
	
	/**
	 * Set request parameter
	 * @param string  $key paramater Key
	 * @param string  $value parameter Value
	 * @param boolean $replace Whether to replace the key if it already exists (default true)
	 * @return void
	 */
	public function setParameter($key, $value, $replace = true) {
		if(!$replace && isset($this->_parameters[$key]))
		{
			$temp = (array)($this->_parameters[$key]);
			$temp[] = $value;
			$this->_parameters[$key] = $temp;
		}
		else
		{
			$this->_parameters[$key] = $value;
		}
	}
	
	/**
	 * Get the response
	 * @return object | false
	 */
	public function getResponse() {
		$this->_parameters['Timestamp'] = gmdate('c');
	
		$params = array();
		foreach ($this->_parameters as $var => $value)
		{
			if(is_array($value))
			{
				foreach($value as $v)
				{
					$params[] = $var.'='.$this->__customUrlEncode($v);
				}
			}
			else
			{
				$params[] = $var.'='.$this->__customUrlEncode($value);
			}
		}
	
		sort($params, SORT_STRING);
	
		$query = implode('&', $params);
	
		$strtosign = $this->_method."\n".$this->_connection->host."\n/\n".$query;
		$query .= '&Signature='.$this->__customUrlEncode($this->__getSignature($strtosign));
	
		$ssl = ($this->_connection->ssl && extension_loaded('openssl'));
		$url = ($ssl ? 'https://' : 'http://').$this->_connection->host.'/?'.$query;
		// Basic setup
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_USERAGENT, 'SimpleDB/php');
	
		if($ssl)
		{
			if(!empty($this->_connection->cainfo))
				curl_setopt($curl, CURLOPT_CAINFO, $this->_connection->cainfo);
			curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->_connection->verifyHost ? 1 : 0));
			curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->_connection->verifyPeer ? 1 : 0));
		}
	
		curl_setopt($curl, CURLOPT_URL, $url);
		curl_setopt($curl, CURLOPT_HEADER, false);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
		curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
		curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
	
		// Request types
		switch ($this->_method) {
			case 'GET': break;
			case 'PUT': case 'POST':
				curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->_method);
				break;
			case 'HEAD':
				curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'HEAD');
				curl_setopt($curl, CURLOPT_NOBODY, true);
				break;
			case 'DELETE':
				curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
				break;
			default: break;
		}
	
		$retry = 0;
		$attempts = 0;
		do
		{
			$retry = 0;
			// Execute, grab errors
			if (curl_exec($curl)) {
				$this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
	
				if($this->response->code == 503) {
					$attempts++;
					$retry = $this->_connection->__executeServiceTemporarilyUnavailableRetryDelay($attempts);
					if($retry > 0) {
						usleep($retry);
					}
				}
			} else {
				throw new ESDBException('CURL::'.curl_errno($curl),curl_error($curl));
			}
		} while($retry > 0);
	
		@curl_close($curl);
	
		// Parse body into XML
		if (isset($this->response->body)) {
			$this->response->rawXML = (string)$this->response->body;
			$this->response->body = simplexml_load_string($this->response->body);
	
			// Grab SimpleDB errors
			if (!in_array($this->response->code, array(200, 204))
					&& isset($this->response->body->Errors)) {
				$this->_errors = array();
				foreach($this->response->body->Errors->Error as $e)
				{
					$this->_errors[]=array(
						'status' => (string)($e->Code),
						'message' => (string)($e->Message)
					);
				}
				throw new ESDBException('SDB::'.$this->_errors[0]['status'],$this->_errors);
			}
		}
		if ($this->response->code !== 200)
			throw new ESDBException('SDB::Unexpected','Unexpected HTTP status');
		return $this->response;
	}
	
	/**
	 * CURL write callback
	 *
	 * @param resource &$curl CURL resource
	 * @param string &$data Data
	 * @return integer
	 */
	private function __responseWriteCallback(&$curl, &$data) {
		$this->response->body .= $data;
		return strlen($data);
	}
	
	/**
	 * Contributed by afx114
	 * URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html
	 * PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode
	 * See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php
	 *
	 * @param string $var String to encode
	 * @return string
	 */
	private function __customUrlEncode($var) {
		return str_replace('%7E', '~', rawurlencode($var));
	}
	
	/**
	 * Generate the auth string using Hmac-SHA256
	 *
	 * @internal Used by SimpleDBRequest::getResponse()
	 * @param string $string String to sign
	 * @return string
	 */
	private function __getSignature($string) {
		return base64_encode(hash_hmac('sha256', $string, $this->_connection->secretKey, true));
	}
		
	
}

?>